package com.hung.hashMap;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.*;
import java.util.function.*;
import java.util.stream.Stream;

/**
     * @serial include
     */
    public  class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

        @Override
        public int size() {
            synchronized (mutex) {return m.size();}
        }
        @Override
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        @Override
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        @Override
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        @Override
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

        @Override
        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        @Override
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        @Override
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        @Override
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

        private transient Set<K> keySet;
        private transient Set<Map.Entry<K,V>> entrySet;
        private transient Collection<V> values;

        @Override
        public Set<K> keySet() {
            synchronized (mutex) {
                if (keySet==null) {
                    keySet = new SynchronizedSet<>(m.keySet(), mutex);
                }
                return keySet;
            }
        }

        @Override
        public Set<Map.Entry<K,V>> entrySet() {
            synchronized (mutex) {
                if (entrySet==null) {
                    entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
                }
                return entrySet;
            }
        }

        @Override
        public Collection<V> values() {
            synchronized (mutex) {
                if (values==null) {
                    values = new SynchronizedCollection<>(m.values(), mutex);
                }
                return values;
            }
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            synchronized (mutex) {return m.equals(o);}
        }
        @Override
        public int hashCode() {
            synchronized (mutex) {return m.hashCode();}
        }
        @Override
        public String toString() {
            synchronized (mutex) {return m.toString();}
        }

        // Override default methods in Map
        @Override
        public V getOrDefault(Object k, V defaultValue) {
            synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
        }
        @Override
        public void forEach(BiConsumer<? super K, ? super V> action) {
            synchronized (mutex) {m.forEach(action);}
        }
        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            synchronized (mutex) {m.replaceAll(function);}
        }
        @Override
        public V putIfAbsent(K key, V value) {
            synchronized (mutex) {return m.putIfAbsent(key, value);}
        }
        @Override
        public boolean remove(Object key, Object value) {
            synchronized (mutex) {return m.remove(key, value);}
        }
        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            synchronized (mutex) {return m.replace(key, oldValue, newValue);}
        }
        @Override
        public V replace(K key, V value) {
            synchronized (mutex) {return m.replace(key, value);}
        }
        @Override
        public V computeIfAbsent(K key,
                Function<? super K, ? extends V> mappingFunction) {
            synchronized (mutex) {return m.computeIfAbsent(key, mappingFunction);}
        }
        @Override
        public V computeIfPresent(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.computeIfPresent(key, remappingFunction);}
        }
        @Override
        public V compute(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.compute(key, remappingFunction);}
        }
        @Override
        public V merge(K key, V value,
                BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.merge(key, value, remappingFunction);}
        }

        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
        static class SynchronizedSet<E>
                extends SynchronizedCollection<E>
                implements Set<E> {
            private static final long serialVersionUID = 487447009682186044L;

            SynchronizedSet(Set<E> s) {
                super(s);
            }
            SynchronizedSet(Set<E> s, Object mutex) {
                super(s, mutex);
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) {
                    return true;
                }
                synchronized (mutex) {return c.equals(o);}
            }
            @Override
            public int hashCode() {
                synchronized (mutex) {return c.hashCode();}
            }
        }
    /**
     * @serial include
     */
    static class SynchronizedCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;

        final Collection<E> c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize

        SynchronizedCollection(Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            mutex = this;
        }

        SynchronizedCollection(Collection<E> c, Object mutex) {
            this.c = Objects.requireNonNull(c);
            this.mutex = Objects.requireNonNull(mutex);
        }

        @Override
        public int size() {
            synchronized (mutex) {return c.size();}
        }
        @Override
        public boolean isEmpty() {
            synchronized (mutex) {return c.isEmpty();}
        }
        @Override
        public boolean contains(Object o) {
            synchronized (mutex) {return c.contains(o);}
        }
        @Override
        public Object[] toArray() {
            synchronized (mutex) {return c.toArray();}
        }
        @Override
        public <T> T[] toArray(T[] a) {
            synchronized (mutex) {return c.toArray(a);}
        }

        @Override
        public Iterator<E> iterator() {
            return c.iterator(); // Must be manually synched by user!
        }

        @Override
        public boolean add(E e) {
            synchronized (mutex) {return c.add(e);}
        }
        @Override
        public boolean remove(Object o) {
            synchronized (mutex) {return c.remove(o);}
        }

        @Override
        public boolean containsAll(Collection<?> coll) {
            synchronized (mutex) {return c.containsAll(coll);}
        }
        @Override
        public boolean addAll(Collection<? extends E> coll) {
            synchronized (mutex) {return c.addAll(coll);}
        }
        @Override
        public boolean removeAll(Collection<?> coll) {
            synchronized (mutex) {return c.removeAll(coll);}
        }
        @Override
        public boolean retainAll(Collection<?> coll) {
            synchronized (mutex) {return c.retainAll(coll);}
        }
        @Override
        public void clear() {
            synchronized (mutex) {c.clear();}
        }
        @Override
        public String toString() {
            synchronized (mutex) {return c.toString();}
        }
        // Override default methods in Collection
        @Override
        public void forEach(Consumer<? super E> consumer) {
            synchronized (mutex) {c.forEach(consumer);}
        }
        @Override
        public boolean removeIf(Predicate<? super E> filter) {
            synchronized (mutex) {return c.removeIf(filter);}
        }
        @Override
        public Spliterator<E> spliterator() {
            return c.spliterator(); // Must be manually synched by user!
        }
        @Override
        public Stream<E> stream() {
            return c.stream(); // Must be manually synched by user!
        }
        @Override
        public Stream<E> parallelStream() {
            return c.parallelStream(); // Must be manually synched by user!
        }
        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
    }
    }
